<?php

/* $Id: client_ip.php 1874 2009-08-03 08:47:32Z xiaxf $ */

/* 
 * 依据客户端 IP + 某个时间段检查当前用户是否在恶意刷网站的某个功能 
 *
 * <code>
 *		// 群组, 判断5分钟内如果客户端回帖了100个, 则直接算是刷回帖
 *		if (check_ip_time_limit('QUN_REPLY_TOPIC', 100, 300)) {
 *			die('请不要恶意回帖');
 *		}
 *		// home 判断 30 分钟内用户访问别人主页的次数达到300次即算恶意刷人气
 *		if (check_ip_time_limit('HOME_VISIT', 300, 1800)) {
 *			die('相关提示语句');
 *		}
 *		// passport 判断20分钟内某IP登录超过20次就要输入验证码
 *		if (check_ip_time_limit('PASSPORT_LOGIN', 20, 1200)) {
 *			// 显示验证码
 *			...
 *		}
 * </code>
 *
 * @param string  $extend_key	各频道自己定义的扩展key, 可根据不同业务扩展这个key
 * @param integer $limit_count	单位时间内要判断的次数值, 最小值为1
 * @param integer $expire_time	IP生存的周期(memcache的expire_time), 默认值5分钟, 最小值为5秒
 * @param string  $ip			客户端IP地址,不传则自动获取, 默认值当前客户端IP
 * @return true / false			true 表示已达到或超过限制, false 未超过限制
 *
 */
function check_ip_time_limit($extend_key, $limit_count, $expire_time = 300, $ip = null)
{
	if (1 > $limit_count || 5 > $expire_time) {
		return false;
	}

	$ip	= trim($ip);
	$ip	|| $ip = get_client_ip_from_ns(true);

	$key    = crc32($_SERVER['SERVER_NAME'].$extend_key.$ip);
	$mcache = get_client_ip_memcache_object();
    $val    = $mcache->get($key);

    if (false === $val) {
    	$mcache->set($key, 1, 0, $expire_time);
    } else {
        $mcache->increment($key);
    }
	$mcache->close();

	return $cnt > $limit_count;
}

/*
 * 获取当前 IP + time 限制中的值
 *
 * @param string $extend_key	各频道自己定义的扩展key
 * @param string $ip			客户端IP, 不传则自动获取客户端IP
 * @return integer
 *
 */
function get_ip_time_limit_count($extend_key, $ip = null)
{
	$ip	= trim($ip);
	$ip	|| $ip = get_client_ip_from_ns(true);
	
	$key = crc32($_SERVER['SERVER_NAME'].$extend_key.$ip);
	$mcache = get_client_ip_memcache_object();
	$cnt    = intval($mcache->get($key));
	$mcache->close();
	
	return $cnt;
}

/*
 * 重置IP+time限制的值
 *
 * @param string $extend_key	各频道自己定义的扩展key
 * @param string $ip			客户端IP, 不传则自动获取客户端IP
 * @return void
 *
 */
function reset_ip_time_limit($extend_key, $ip = null) 
{
	$ip	= trim($ip);
	$ip	|| $ip = get_client_ip_from_ns(true);

	$key = crc32($_SERVER['SERVER_NAME'].$extend_key.$ip);
	$mcache = get_client_ip_memcache_object();
	$mcache->delete($key);
	$mcache->close();
}

/*
 * 获取保存IP限制的 memcache 
 *
 * @param void
 * @return memcache object
 *
 */
function get_client_ip_memcache_object()
{
	$mcache = new memcache;
	$mcache->addServer('10.10.9.43', 11556, false);
	$mcache->addServer('10.10.9.44', 11556, false);
	$mcache->addServer('10.10.9.45', 11556, false);
	$mcache->addServer('10.10.9.46', 11556, false);
	return $mcache;
}

/*
 * 函数功能: 获取客户端的真实IP地址
 * 
 * 为什么要用这个函数?
 * 因为我们线上Web服务器绝大部分都处于Netscaler(简称NS)后面，客户端访问的地址统一由NS调度
 * 由NS调度的访问其实就是NS做了一层代理, 这期间就有一个问题, 因为真实的地址是内部IP请求的
 * 当我们的应用去请获取 $_SERVER["REMOTE_ADDR"] 的时候, 得到的就是 NS 的内部 IP, 获取不了
 * 真正的客户端 IP 地址.
 * 
 * 当请求经过 NS 调度之后, NS 会把客户端的真实 IP 附加到 HTTP_CLIENT_IP 后，我们要提取的就
 * 是这个地址. 
 * 
 * 如测试数据: 
 * [HTTP_CLIENT_IP] => 192.168.2.251, 192.168.3.252, 218.82.113.110
 * 这条信息是我测试的结果, 前面两个 IP 是我伪造的, 最后那个 IP 才是我真实的地址.
 * 
 * 同样我也测试过通过代理的数据
 * [HTTP_X_FORWARDED_FOR] => 192.168.2.179, 123.45.67.78 64.191.50.54
 * 前面两个IP是我伪造的, 最后面那个地址才是 proxy 的真实地址
 * 
 * 提醒: 
 * HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR 都可以在客户端伪造, 不要轻易直接使用这两个值, 因为
 * 恶意用户可以在里面输入PHP代码, 或者像伪造 N 个', 让你的程序执行有问题, 如果要直接使用这
 * 两个值的时候最简单的应该判断一下长度(最长15位), 或用正则匹配一下是否是一个有效的IP地址
 * 
 * 参数:
 * 
 * @param string $proxy_override, [true|false], 是否优先获取从代理过来的地址
 * @return string 
 *
 */
function get_client_ip_from_ns($proxy_override = false) 
{
	if ($proxy_override) {
		/* 优先从代理那获取地址或者 HTTP_CLIENT_IP 没有值 */
		$ip = empty($_SERVER["HTTP_X_FORWARDED_FOR"]) ? $_SERVER["HTTP_CLIENT_IP"] : $_SERVER["HTTP_X_FORWARDED_FOR"];
	} else {
		/* 取 HTTP_CLIENT_IP, 虽然这个值可以被伪造, 但被伪造之后 NS 会把客户端真实的 IP 附加在后面 */
		$ip = empty($_SERVER["HTTP_CLIENT_IP"]) ? NULL : $_SERVER["HTTP_CLIENT_IP"];
	}

	if (empty($ip)) {
		$ip = $_SERVER['REMOTE_ADDR'];
	}

	/* 真实的IP在以逗号分隔的最后一个, 当然如果没用代理, 没伪造IP, 就没有逗号分离的IP */
	if ($p = strrpos($ip, ",")) {
		$ip = substr($ip, $p+1);
	}

	return trim($ip);
}

function GetIP(){
	if(!empty($_SERVER["HTTP_CLIENT_IP"])){
		$cip = $_SERVER["HTTP_CLIENT_IP"];
	}else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"])){
		$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
	}else if(!empty($_SERVER["REMOTE_ADDR"])){
		$cip = $_SERVER["REMOTE_ADDR"];
	}else{
		$cip = '';
	}
	preg_match("/[\d\.]{7,15}/", $cip, $cips);
	$cip = isset($cips[0]) ? $cips[0] : 'unknown';
	unset($cips);
	return $cip;
}

/*
 * 检查IP是否在黑名单中
 *
 * @param string $ip	要检查的IP, 如果不传递此值会自动获取当前客户端IP
 * @return true /false
 *
 */
function check_ip_blacklist($ip = null) 
{
	$ip || $ip = get_client_ip_from_ns();

	$blacklist = array('220.194.46.104', '60.190.222.198', '118.122.176.40', '61.28.5.250', '60.190.222.198', '202.103.67.98', '125.65.77.71');

	return in_array($ip, $blacklist);
}

/*
 * 检查 IP 是否在白名单中
 *
 * @param string $ip 要检查的 ip
 * @return true / false
 *
 */
function check_ip_whitelist($ip = null) 
{
	$ip || $ip = get_client_ip_from_ns(true);

	$whitelist = array('61.129.60.81','61.129.60.82','61.129.60.83','61.129.60.84','61.129.60.85','61.129.60.86');

	return in_array($ip, $whitelist);
}

/*
 * 检查客户端从什么地方过来的请求
 *
 * <code>
 *		// 严格检查此页面来源中域名必须包含 .51.com
 *		if (!check_client_referer(true, '.51.com')) {
 *			die('非法提交的数据');
 *		}
 *		// 松散检查来源url中必须包含.51.com
 *		if (!check_client_referer()) {
 *			die('非法提交的数据');
 *		}
 * </code>
 * 
 * @param bool   $restrict  是否进行严格的查检, 此方式为用正则对host进行匹配
 * @param string $allow		允许哪些 referer 过来请求
 * @return true / false		在允许的列表内返回true
 *
 */
function check_client_referer($restrict = true, $allow = '.51.com') 
{
	$referer = isset($_SERVER['HTTP_REFERER']) ? trim($_SERVER['HTTP_REFERER']) : null;
	if (empty($referer)) { return true;	} /* 空的 referer 直接允许 */

	if ($restrict) {

		/* 更加严格的查检, 此值为true时, allow 可以输入正则来匹配 */
		$url = parse_url($referer);
		/* host 为空时直接返回不false */
		if (empty($url['host'])) { return false; } 

		/* 将正则中的.替换掉为\.真正匹配.再进行匹配 */
		$allow = '/'.str_replace('.', '\.', $allow).'/';
		return 0 < preg_match($allow, $url['host']);
	}

	return false !== strpos($referer, $allow);
}

?>
